// Fill out your copyright notice in the Description page of Project Settings.

#include "Isalong2.h"
#include "AI/Navigation/NavigationPath.h"
#include "BeHurtable.h"
#include "CPlayerCharacter.h"
#include "CWeaponHurtType.h"
#include "RootZombieBase.h"


// Sets default values
ARootZombieBase::ARootZombieBase()
{
	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	GetCapsuleComponent()->SetCapsuleHalfHeight(88);
	GetCapsuleComponent()->SetCapsuleRadius(34);

	GetMesh()->SetRelativeLocation(FVector(0, 0, -88));
	GetMesh()->SetRelativeRotation(FRotator(0, -90, 0));
	GetMesh()->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
	GetMesh()->SetCollisionProfileName(TEXT("BuildingTrace"));

	_forwardArrow = CreateDefaultSubobject<UArrowComponent>(TEXT("ForwardArrow"));
	_forwardArrow->AttachTo(GetCapsuleComponent());

	_viewSphere = CreateDefaultSubobject<USphereComponent>(TEXT("ViewSight"));
	SetSightSphereSize(NormalSight);
	_viewSphere->SetCollisionProfileName(TEXT("OverlapAll"));
	_viewSphere->AttachTo(GetCapsuleComponent());
	_viewSphere->OnComponentBeginOverlap.AddDynamic(this, &ARootZombieBase::OnROIBeginOverlap);
	_viewSphere->OnComponentEndOverlap.AddDynamic(this, &ARootZombieBase::OnROIEndOverlap);

	_attackBox = CreateDefaultSubobject<UBoxComponent>(TEXT("AttackBox"));
	_attackBox->AttachTo(GetCapsuleComponent());
	_attackBox->SetRelativeScale3D(FVector(1, 2, 2));
	_attackBox->SetRelativeLocation(FVector(80, 0, 0));
	_attackBox->SetCollisionProfileName(TEXT("OverlapAll"));
	_attackBox->OnComponentBeginOverlap.AddDynamic(this, &ARootZombieBase::OnPreAttackActorBeginOverlap);
	_attackBox->OnComponentEndOverlap.AddDynamic(this, &ARootZombieBase::OnPreAttackActorEndOverlap);

	_turnL90 = LoadObject<UAnimMontage>(NULL, TEXT("AnimMontage'/Game/BrainInVst/ZombieRootMotion/Zombie_Turn_L_90_2_Montage.Zombie_Turn_L_90_2_Montage'"));
	_turnL180 = LoadObject<UAnimMontage>(NULL, TEXT("AnimMontage'/Game/BrainInVst/ZombieRootMotion/Zombie_Turn_L_180_2_Montage.Zombie_Turn_L_180_2_Montage'"));
	_turnR90 = LoadObject<UAnimMontage>(NULL, TEXT("AnimMontage'/Game/BrainInVst/ZombieRootMotion/Zombie_Turn_R_90_4_Montage.Zombie_Turn_R_90_4_Montage'"));
	_turnR180 = LoadObject<UAnimMontage>(NULL, TEXT("AnimMontage'/Game/BrainInVst/ZombieRootMotion/Zombie_Turn_R_180_4_Montage.Zombie_Turn_R_180_4_Montage'"));

	_hurtBack = LoadObject<UAnimMontage>(NULL, TEXT("AnimMontage'/Game/TposeZombie1/Animations/z1_hit_back_Montage.z1_hit_back_Montage'"));

	_attack1 = LoadObject<UAnimMontage>(NULL, TEXT("AnimMontage'/Game/TposeZombie1/Animations/ZombieAttack_1_Naked.ZombieAttack_1_Naked'"));
	_attack2 = LoadObject<UAnimMontage>(NULL, TEXT("AnimMontage'/Game/TposeZombie1/Animations/ZombieAttack_2_Naked.ZombieAttack_2_Naked'"));
	_attack3 = LoadObject<UAnimMontage>(NULL, TEXT("AnimMontage'/Game/TposeZombie1/Animations/ZombieAttack_3_Naked.ZombieAttack_3_Naked'"));
}

// Called when the game starts or when spawned
void ARootZombieBase::BeginPlay()
{
	Super::BeginPlay();

}

// Called every frame
void ARootZombieBase::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (!_animEnable) { return; }

	// go straight process
	GoStraightTick(DeltaTime);

	// the move to process
	MoveToTick(DeltaTime);

	// update the zombie state
	StateCheckTick(DeltaTime);

	// Free action
	FreeTick(DeltaTime);

	// search action
	SearchTick(DeltaTime);

	// attack tick
	AttackTick(DeltaTime);
}

// Called to bind functionality to input
void ARootZombieBase::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
	Super::SetupPlayerInputComponent(InputComponent);

}

void ARootZombieBase::OnHurt(AActor* shotter, FVector impactNormal, FVector hitLocation, int HurtType, float hurtValue, UPrimitiveComponent* hitComponent)
{
	HurtBack();
	if (HurtType==CWeaponHurtType::Sword)
	{
		Die();
	}
	else
	{
		_lifeValue -= 0.6;
		if (_lifeValue<0)
		{
			Die();
		}
	}
}

void ARootZombieBase::PerceptionOutlineOn()
{
	GetMesh()->SetCustomDepthStencilValue(252);
	GetMesh()->bRenderCustomDepth = true;
}

void ARootZombieBase::PerceptionOutlineOff()
{
	GetMesh()->SetCustomDepthStencilValue(0);
	GetMesh()->bRenderCustomDepth = false;
}

void ARootZombieBase::InitInfo(USkeletalMesh* MainSkeletalMesh, TSubclassOf<UAnimInstance> ZombieAnimBP)
{
	GetMesh()->SetSkeletalMesh(MainSkeletalMesh);
	GetMesh()->SetAnimInstanceClass(ZombieAnimBP);
}

void ARootZombieBase::GoStraightTick(float DeltaTime)
{
	if (!_isGoStraight)
	{
		return;
	}
	if (_isTurning)
	{
		StopMoveForward();
		return;
	}

	FVector forward3dTemp = _forwardArrow->GetForwardVector();
	FVector2D a1 = FVector2D(forward3dTemp.X, forward3dTemp.Y);
	float valCos = FVector2D::DotProduct(a1, FVector2D(_goStraightTargetDirection));
	valCos = (180.f) / PI * FMath::Acos(valCos);
	int sign = FVector2D::CrossProduct(a1, FVector2D(_goStraightTargetDirection)) > 0 ? 1 : -1;
	if (valCos<45)
	{
		AddActorWorldRotation((FRotationMatrix::MakeFromX(FVector(_goStraightTargetDirection.X, _goStraightTargetDirection.Y, 0)).Rotator() - GetActorRotation())*DeltaTime);
		MoveForward();
		// just lerp to 
		return;
	}
	else if (valCos >= 45 && valCos < 135)
	{
		// turn 90 degree
		if (sign > 0) { TurnRight90(); return; }
		else { TurnLeft90(); return; }
	}
	else
	{
		if (sign > 0) { TurnRight180(); return; }
		else { TurnLeft180(); return; }
	}
}

void ARootZombieBase::MoveToTick(float DeltaTime)
{
	if (!_isMovingTo) return;
	if ((_currentTaget - GetActorLocation()).Size()<150) // if near
	{
		if (_pathPointer + 1 == _path.Num()) // finish
		{
			StopMoveTo();
			return;
		}
		_pathPointer++;
		_currentTaget = _path[_pathPointer].Location;
	}
	GoStraight(_currentTaget - GetActorLocation());
}

void ARootZombieBase::StateCheckTick(float DeltaTime)
{
	// update attention
	float attentionBaseValue = -0.05;
	for (int i = 0; i<_roiActor.Num(); i++)
	{
		if (Cast<ACharacter>(_roiActor[i])!=nullptr&&InSight(_roiActor[i]))
		{
			attentionBaseValue += 1;
		}
	}
	AddAttention(attentionBaseValue);
	_isShamble = _attention > 100;
	// for attack check
	// character first
	for (int i=0;i<_roiActor.Num();i++)
	{
		if (Cast<ACPlayerCharacter>(_roiActor[i])!=nullptr)
		{
			_attack_target = _roiActor[i];
			DoAction(Action_Attack);
			// go and attack
			return;
		}
	}
	// if no character select wall
	for (int i = 0; i<_roiActor.Num(); i++)
	{
		if (Cast<IBeHurtable>(_roiActor[i]) != nullptr&&InSight(_roiActor[i]) && _isShamble)
		{
			_attack_target = _roiActor[i];
			DoAction(Action_Attack);
			// go and attack
			return;
		}
	}
	// here indicate no sight and _isShamble
	if (_isShamble&&(Cast<ACharacter>(_attack_target)!=nullptr)) // this must bu search
	{
		DoAction(Action_Search);
		return;
	}
	DoAction(Action_Free);
}

void ARootZombieBase::FreeTick(float DeltaTime)
{
	if (_currentAction != Action_Free)return;
	if (_free_choose)
	{
		_free_selectNewPointTimeRemain -= DeltaTime; //cal for new time remain
		if (_free_selectNewPointTime - _free_selectNewPointTimeRemain>_free_selectNewPointTime)
		{
			FreeRandom();
			return;
		}
		// if not end
		GoStraight(_freeTargetDirection);
	}
	else
	{
		_free_idleTimeRemain -= DeltaTime;
		if (_free_idleTime - _free_idleTimeRemain>_free_idleTime)
		{
			FreeRandom();
			return;
		}
		StopStraight();
	}
}

void ARootZombieBase::FreeRandom()
{
	// select new choose
	_free_choose = FMath::RandRange(0, 1) == 0;
	if (_free_choose)
	{
		float radius = FMath::RandRange(0, 3.1415926 * 2);
		_freeTargetDirection = FVector(FMath::Cos(radius), FMath::Sin(radius), 0);
		_free_selectNewPointTimeRemain = _free_selectNewPointTime;
	}
	else
	{
		_free_idleTimeRemain = _free_idleTime;
	}
}

void ARootZombieBase::SearchTick(float DeltaTime)
{
	if (_currentAction != Action_Search) return;
	_search_timestimeRemain -= DeltaTime;
	if (_search_timestime - _search_timestimeRemain>_search_timestime)
	{
		_search_timestimeRemain = _search_timestime;
		// change angle
		_search_randomAngle += (3.1415926 / 2 - _search_randomAngle) / 2;
		float tempTargetAngle = FMath::RandRange(-_search_randomAngle, _search_randomAngle);
		FVector targetDirection = _forwardArrow->GetForwardVector().RotateAngleAxis(tempTargetAngle, _forwardArrow->GetUpVector());
		GoStraight(targetDirection);
	}
}

void ARootZombieBase::AttackTick(float DeltaTime)
{
	if (_currentAction != Action_Attack)return;
	MoveTo(_attack_target->GetActorLocation(), true);
	for (int i = 0; i < _preAttackActor.Num(); i++)
	{
		float angle = FVector::DotProduct((_preAttackActor[i].GetActor()->GetActorLocation() - GetActorLocation()).GetSafeNormal(), _forwardArrow->GetForwardVector());
		if ((_preAttackActor[i].GetActor()->GetActorLocation() - GetActorLocation()).Size()<150 && angle<1.0467) // 1.0467 = 60/180 * pi
		{
			Attack();
		}
	}
}

void ARootZombieBase::AddAttention(float val)
{
	_attention += val;
	if (_attention>200)
	{
		_attention = 200;
	}
	if (_attention<0)
	{
		_attention = 0;
	}
}

void ARootZombieBase::DoAction(int action)
{
	if (_currentAction == action) { return; }
	_currentAction = action;
	if (action == Action_Free) //free
	{
		SetSightSphereSize(NormalSight);
		StopMoveTo();
		_isShamble = false;
	}
	if (action == Action_Search) //search
	{
		SetSightSphereSize(AngrySight);
		_isShamble = true;
	}
	if (action == Action_Attack) //attack
	{
		SetSightSphereSize(AngrySight);
		_isShamble = true;
	}
}

bool ARootZombieBase::InSight(AActor* actor)
{
	FVector focalCenter = GetActorLocation() + _forwardArrow->GetForwardVector()*LookBackOffset;
	if ((actor->GetActorLocation() - focalCenter).Size()>_currentSightSphereSize)
	{
		return false;
	}
	return true;
}

void ARootZombieBase::SetSightSphereSize(float val)
{
	_viewSphere->SetSphereRadius(val);
	_currentSightSphereSize = val;
}

void ARootZombieBase::Die()
{
	GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	StopMoveTo();
	_animEnable = false;
	GetMesh()->SetCollisionProfileName(TEXT("IgnoreOnlyPawn"));
	GetMesh()->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
	GetMesh()->SetSimulatePhysics(true);
}

void ARootZombieBase::MoveForward()
{
	_isMovingForward = true;
}

void ARootZombieBase::StopMoveForward()
{
	_isMovingForward = false;
}

void ARootZombieBase::GoStraight(FVector direction)
{
	_isGoStraight = true;
	_goStraightTargetDirection = direction;
}

void ARootZombieBase::StopStraight()
{
	StopMoveForward();
	_isGoStraight = false;
}

void ARootZombieBase::TurnRight90()
{
	if (_isTurning)
	{
		return;
	}
	_isAttackReloading = false;
	_isTurning = true;
	GetMesh()->GetAnimInstance()->Montage_Play(_turnR90);
}

void ARootZombieBase::TurnRight180()
{
	if (_isTurning)
	{
		return;
	}
	_isAttackReloading = false;
	_isTurning = true;
	GetMesh()->GetAnimInstance()->Montage_Play(_turnR180);
}

void ARootZombieBase::TurnLeft90()
{
	if (_isTurning)
	{
		return;
	}
	_isAttackReloading = false;
	_isTurning = true;
	GetMesh()->GetAnimInstance()->Montage_Play(_turnL90);
}

void ARootZombieBase::TurnLeft180()
{
	if (_isTurning)
	{
		return;
	}
	_isAttackReloading = false;
	_isTurning = true;
	GetMesh()->GetAnimInstance()->Montage_Play(_turnL180);
}

void ARootZombieBase::TurnComplete()
{
	_isTurning = false;
}

void ARootZombieBase::HurtBack()
{
	GetMesh()->GetAnimInstance()->Montage_Play(_hurtBack);
	_isTurning = false;
	_isAttackReloading = false;
}

void ARootZombieBase::Attack()
{
	if (_isAttackReloading) { return; }
	// some error cause by animation
	_isTurning = false;

	_isAttackReloading = true;
	int choose = FMath::RandRange(0, 2);
	switch (choose)
	{
	case 0:
		GetMesh()->GetAnimInstance()->Montage_Play(_attack1);
		break;
	case 1:
		GetMesh()->GetAnimInstance()->Montage_Play(_attack2);
		break;
	case 2:
		GetMesh()->GetAnimInstance()->Montage_Play(_attack3);
		break;
	}
}

void ARootZombieBase::AttackAnimComplete()
{
	_isAttackReloading = false;
}

void ARootZombieBase::AttackFire()
{
	for (int i=0;i<_preAttackActor.Num();i++)
	{
		AActor* preAtk = _preAttackActor[i].GetActor();
		// do some effect start
		if (_preAttackActor[i].GetComponent() != nullptr&&_preAttackActor[i].GetComponent()->IsSimulatingPhysics())
		{
			_preAttackActor[i].GetComponent()->AddImpulseAtLocation(5000 * (preAtk->GetActorLocation() - GetActorLocation()).SafeNormal(), _preAttackActor[i].Location);
		}
		// do some effect end 

		// apply damage
		IBeHurtable* ibh = Cast<IBeHurtable>(preAtk);
		if (ibh!=nullptr)
		{
			ibh->OnHurt(this, (preAtk->GetActorLocation() - GetActorLocation()).SafeNormal(), _attackBox->GetComponentLocation(), CWeaponHurtType::Hit, _attack_hurtValue, _preAttackActor[i].GetComponent());
		}
	}
}

void ARootZombieBase::MoveTo(FVector destLocation, bool usePathFinding/*=true*/)
{
	_isMovingTo = true;
	_pathPointer = 0;
	_destLocation = destLocation;
	if (usePathFinding)
	{
		// find a path first
		UNavigationPath* findedPath = UNavigationSystem::GetCurrent(GetWorld())->FindPathToLocationSynchronously(this, GetActorLocation(), destLocation);
		if (findedPath == nullptr)
		{
			// when can`t find a path //the current target is the dest target , the pathfinding degeneration to go to action
			_currentTaget = _destLocation;
			return;
		}

		// other wise ,found the path
		_path = findedPath->GetPath()->GetPathPoints();
		_currentTaget = _path[0].Location;
		/*for (int i=0;i<_path.Num()-1;i++)
		{
		DrawDebugLine(GetWorld(), _path[i].Location, _path[i + 1].Location, FColor(255, 255, 255),true);
		}*/
	}
	else
	{
		_currentTaget = _destLocation;
	}
}

void ARootZombieBase::StopMoveTo()
{
	_isMovingTo = false;
	StopStraight();
}

void ARootZombieBase::OnROIBeginOverlap( AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (_roiActor.Contains(OtherActor) || Cast<ARootZombieBase>(OtherActor)!=nullptr||Cast<IBeHurtable>(OtherActor)==nullptr) { return; }
	_roiActor.Add(OtherActor);
}

void ARootZombieBase::OnROIEndOverlap( AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	if (!_roiActor.Contains(OtherActor)) { return; }
	_roiActor.Remove(OtherActor);
}

void ARootZombieBase::OnPreAttackActorBeginOverlap(AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor == this||Cast<ARootZombieBase>(OtherActor)!=nullptr) { return; }
	for (int i=0;i<_preAttackActor.Num();i++)
	{
		if (_preAttackActor[i].GetActor() == OtherActor) return;
	}
	_preAttackActor.Add(SweepResult);
}

void ARootZombieBase::OnPreAttackActorEndOverlap(AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	for (int i=0;i<_preAttackActor.Num();i++)
	{
		if (_preAttackActor[i].GetActor()==OtherActor)
		{
			_preAttackActor.RemoveAt(i);
			return;
		}
	}
}

